package model.materiasYCatedras;
import java.util.List;
import java.lang.Math;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map.Entry;
import java.util.Observable;

import javax.management.RuntimeErrorException;

import model.alumnos.AlumnoEnCarrera;
import model.carrerasYPlanes.Carrera;
import model.interfaces.AlumnoIMPL;
import model.interfaces.CatedraIMPL;
import model.tps.EntregaTpPorAlumno;
import model.tps.Tema;
import model.tps.Tp;


public class Catedra extends Observable implements CatedraIMPL {
	private MateriaPorPlanDeEstudio materia;
	private Staff staff;
	private List<AlumnoEnCatedra> alumnos = new ArrayList<AlumnoEnCatedra>();
	private String nombre; 
	private int totalDeClases;
	private int cupoMaximo;
	private List<DiaYhorario> horario;
	private List<Tp> tps = new ArrayList<Tp>();
	
	// Getters && Setters //

	public List<DiaYhorario> getHorario() {
		return horario;
	}

	public List<Tp> getTps() {
		return tps;
	}

	public void setTps(List<Tp> tps) {
		this.tps = tps;
	}

	public void setHorario(List<DiaYhorario> horario) {
		this.horario = horario;
	}

	public void setHorarios(List<DiaYhorario> horarios) {
		this.horario = horarios;
	}
	
	public MateriaPorPlanDeEstudio getMateria() {
		return materia;
	}

	public int getTotalDeClases() {
		return totalDeClases;
	}

	public void setTotalDeClases(int totalDeClases) {
		this.totalDeClases = totalDeClases;
	}

	public int getCupoMaximo() {
		return cupoMaximo;
	}

	public void setCupoMaximo(int cupoMaximo) {
		this.cupoMaximo = cupoMaximo;
	}

	public void setMateria(MateriaPorPlanDeEstudio materia) {
		this.materia = materia;
	}

	public Staff getStaff() {
		return staff;
	}

	public void setStaff(Staff staff) {
		this.staff = staff;
	}

	public List<AlumnoEnCatedra> getAlumnos() {
		return alumnos;
	}

	public void setAlumnos(List<AlumnoEnCatedra> alumnos) {
		this.alumnos = alumnos;
	}

	public String getNombre() {
		return nombre;
	}

	public void setNombre(String nombre) {
		this.nombre = nombre;
		
	}
	
	private AlumnoEnCatedra primerAlumno(){
		return this.getAlumnos().get(0);
	}

	// Constructor //
	/**
	 * Necesita estos parametros para 
	 * que se pueda instanciar una catedra, si o si
	 * de otra forma seria una clase inconsistente al
	 * momento de ser creada
	 * 
	 * @param materia
	 * @param nombre
	 * @param staff
	 * @param cupoMax
	 */
	public Catedra(MateriaPorPlanDeEstudio materia, String nombre, Staff staff, int cupoMax){
		this.setNombre(nombre);
		this.setMateria(materia);
		this.setStaff(staff);
		this.setCupoMaximo(cupoMax);
		this.alumnos = new ArrayList<AlumnoEnCatedra>();
		this.horario = new ArrayList<DiaYhorario>();
		materia.getCatedras().add(this);
	}
	
	public Catedra(MateriaPorPlanDeEstudio materia, String nombre){
		this.setNombre(nombre);
		this.setMateria(materia);
		this.setCupoMaximo(20);
		this.alumnos = new ArrayList<AlumnoEnCatedra>();
		this.horario = new ArrayList<DiaYhorario>();
		materia.getCatedras().add(this);
	}
	
	public Catedra(){
		super();
		this.horario = new ArrayList<DiaYhorario>();
	}
	
	
	public Carrera getCarrera(){
		return this.materia.getPlanDeEstudio().getCarrera();
	}
	
	public Integer cantidadDeAlumnos(){
		return this.getAlumnos().size();
	}
	
	public Integer cantidadTps(){
		return this.getTps().size();
	}
	
	
	////
	
	public List<AlumnoEnCatedra> alumnosInscriptos(){
		return this.getAlumnos();
	}
	
	/**
	 * Se encarga de recorrer la lista de alumnos
	 * y guardar en una nueva lista solo los que 
	 * el promedio de las notas es superior a 7
	 * 
	 * @return
	 */
	public List<AlumnoEnCatedra> alumnosAprobados(){
		List<AlumnoEnCatedra> aprobados = new ArrayList<AlumnoEnCatedra>();
		for ( AlumnoEnCatedra alumno : this.getAlumnos())
			if (alumno.promedio() > 7){
				aprobados.add(alumno);
			}
		return aprobados;
	}
	
	/**
	 * Se encarga de recorrer la lista de alumnos
	 * y guardar en una nueva lista solo los que 
	 * el promedio de las notas es inferior a 7
	 * @return
	 */
	public List<AlumnoEnCatedra> alumnosDesaprobados(){
		List<AlumnoEnCatedra> desaprobados = new ArrayList<AlumnoEnCatedra>();
		for (AlumnoEnCatedra alumno: this.getAlumnos()){
			if (alumno.promedio() < 7){
				desaprobados.add(alumno);
			}
		}
		return desaprobados;
	}
	
	/**
	 * Ya teniendo la lista de aprobados y desaprobados
	 * lo que hago es chequear que el alumno no este en 
	 * ninguno de ellos.
	 * @return
	 */
	public List<AlumnoEnCatedra> pendientesDeAprobacion(){
		List<AlumnoEnCatedra> pendientes = new ArrayList<AlumnoEnCatedra>();
		for (AlumnoEnCatedra alumno: this.getAlumnos()){
			if (!this.alumnosAprobados().contains(alumno) & this.alumnosDesaprobados().contains(alumno)){
				pendientes.add(alumno);
			}
		}
		return pendientes;
	}
	
	/**
	 * Este metodo se usa para buscar un 
	 * alumno puntual dentro del 
	 * listado completo de alumnos,
	 * el alumno que deseo buscar es el 
	 * que se pasa por parametro
	 * 
	 * @param unAlumno
	 * 
	 * @return
	 */
	public AlumnoEnCatedra buscarUnAlumno(AlumnoEnCatedra unAlumno){
		AlumnoEnCatedra alumnoBuscado = null;
		for (AlumnoEnCatedra alumno: this.getAlumnos()){
			if (alumno.equals(unAlumno)){
				alumnoBuscado = unAlumno;
			}
		}
		return alumnoBuscado;
		
	}
	
	/**
	 *  En este metodo devuelve la lista de notas de un alumno
	 *  para ello se necesita que el alumno tenga una Lista de notas.
	 *  
	 *  
	 * @param unAlumno
	 * 
	 * @return
	 */
	public List<Integer> notasAlumno(AlumnoEnCatedra unAlumno){
		return this.buscarUnAlumno(unAlumno).getNotas();
	}
	
	/**
	 * Calcula el presentismo de un alumno 
	 * el cual se recibe por parametro.
	 * @param alumno
	 * @return
	 */
	public double presentismo(AlumnoEnCatedra alumno){
		double d = (this.getTotalDeClases() - alumno.cantidadDeAusentes()) / (double) this.getTotalDeClases(); 
		double porcTot = d * 100 ;
		return porcTot;	
	}
	
	public Boolean tieneCupo(){
		return this.getAlumnos().size() < this.getCupoMaximo();
	}
	
	/**
	 * Se usa este metodo para evaluar que no se superpongan 
	 * horarios con otras catedras.
	 * 
	 * @param unHorario
	 * @return
	 */
	
	public boolean seSuperponenHorariosCatedras(List<Catedra> catedras){
		for (Catedra unaCatedra: catedras ){
			for (DiaYhorario unHorario: unaCatedra.getHorario())
				if (this.seSuperponenHorarios(unHorario)){
					return true;
				}
		}
		return false;
	}
	
	public boolean seSuperponenHorarios(DiaYhorario unHorario){ 
		for (DiaYhorario diayhorario: this.getHorario()){
			if (diayhorario.seSuperponenHorarios(unHorario.getDiaCursada(), unHorario.getHoraInicio(), unHorario.getHoraFin())){
				return true;
			}
		}
		return false;
	}
	
	/**
	 * El promedio total de notas de un alumno puntual
	 * 
	 * @param alumno
	 * @return
	 */
	public Double promedioNotas(AlumnoEnCatedra alumno){
		return alumno.promedio();
	}
	
	/**
	 * Cambia el staff de la catedra por uno nuevo 
	 * previamente graba la catedra en el historial.
	 * 
	 * @param unStaff
	 */
	public void cambiarStaff(Staff unStaff){
		this.grabarHistorialCatedra(this);
		this.setStaff(unStaff);
	}
	
	private void grabarHistorialCatedra(Catedra unaCatedra){
		HistorialCatedra.getInstance().agregarCatedraAlhistorial(unaCatedra);
	}
	
	/**
	 * Inscribe dentro de la catedra un 
	 * alumno que se recibe por parametro.
	 * @param alumno
	 */
	public void inscribirAlumno(AlumnoEnCarrera alumno){
		if (!(alumno.getAlumno().getPersona() == this.getMateria().getDocenteTitular().getPersona())){
			AlumnoEnCatedra alumnoEnCatedra = new AlumnoEnCatedra(alumno);
			this.getAlumnos().add(alumnoEnCatedra);
			this.addObserver(alumnoEnCatedra);
		}
	}
	
	/**
	 * Agrega la materia a la lista de materias cursadas del 
	 * alumno, para que esto suceda el promedio debe ser >= 7
	 */
	public void cierreDeNotas(){
		for ( AlumnoEnCatedra alumno : this.getAlumnos()) {
            if (alumno.promedio() >= 7){
            	alumno.getAlumno().agregarMateriaCursada(this.getMateria(), alumno.promedio());
            }
        }
	}
	
	public Persona getProfesor(){
		return this.getMateria().getDocenteTitular().getPersona();	
	}
	
	public void agregarHorario(DiaYhorario horario){
		this.horario.add(horario);
	}
	
	/**
	 * Con los cambios de horarios se notifica a los 
	 * alumnos de la catedra.
	 * 
	 * @param horarioAnterior
	 * 
	 * @param nuevoHorario
	 */
	public void cambiarHorarioCatedra(DiaYhorario horarioAnterior, DiaYhorario nuevoHorario){
		int index = this.getHorario().indexOf(horarioAnterior);
		this.getHorario().add(index, nuevoHorario);
		this.setChanged();
		this.notifyObservers();
	}

	@Override
	public List<AlumnoIMPL> getInscriptos() {
		List<AlumnoIMPL> list = new ArrayList<AlumnoIMPL>();
		for ( AlumnoEnCatedra alumno: this.getAlumnos()) {
			list.add((AlumnoIMPL) alumno.getAlumno());
		}
		return list;
	}
	
	public String toString(){
		return this.getNombre();
	}
	
	/**
	 * Este metodo se encargarga de buscar
	 * todos los alumnos que hayan aprobado un 
	 * Tp con un tema en particular.
	 * 
	 * En caso que hayan dos tp con un mismo tema
	 * se buscara por el primero, no se tomaran en 
	 * cuenta los dos tps.
	 * 
	 * @param unTema
	 * Recibe un tema para asi buscar todos los tps 
	 * que tengan un tema. 
	 * 
	 * @return
	 */
	
	public List<AlumnoEnCatedra> alumnosQueAprobaronTpConUnTema(Tema unTema){
		Tp tpConTema = this.buscarTpConUnTema(unTema);
		List<AlumnoEnCatedra> alumnosQueAprobaronUnTp = new ArrayList<AlumnoEnCatedra>();
		for (AlumnoEnCatedra alumno : this.getAlumnos()) {
			if(alumno.aproboUnTp(tpConTema)){
				alumnosQueAprobaronUnTp.add(alumno);
			}
		}
		return alumnosQueAprobaronUnTp;
	}
	
	/**
	 * Dentro de la lista de Tp's que tiene la catedra
	 * busca un Tp que tenga un determinado tp que es pasado por
	 * parametro
	 * 
	 * @param unTema
	 * @return
	 */
	public Tp buscarTpConUnTema(Tema unTema){
		for (Tp tp : this.getTps()) {
			if(tp.tpContieneUnTema(unTema)){
				return tp;
			}
		}
		return null;
	}
	
	public void agregarTp(Tp unTp){
		this.getTps().add(unTp);
	}
	
	public Integer notaMasAltaEnUnEntrega(Tp tp){
		Integer mayorNota = this.getAlumnos().get(0).notaEnTp(tp);
		for (AlumnoEnCatedra alu : this.getAlumnos()) {
			if(mayorNota < alu.notaEnTp(tp)){
				mayorNota = alu.notaEnTp(tp);
			}
		}
		return mayorNota;
	}
	
	
	
	
	/**
	 * Este metodo devuelve la mejor entrega para cada de tp
	 * recorriendo uno por uno los tps y pidiendole la mejor
	 * cada tp guarda una lista de EntregaTpPorAlumno
	 * y esta ultima clase sabe compararse con otra 
	 * mediante el metodo compareTo:
	 *
	 * 
	 * @return
	 */
	public List<EntregaTpPorAlumno> tpsPorMejorEntrega(){
		List<EntregaTpPorAlumno> mejoresEntregasPorTp = new ArrayList<EntregaTpPorAlumno>();
		for (Tp tp : this.getTps()) {
			mejoresEntregasPorTp.add(Collections.min(tp.getEntregas()));
		}
		return mejoresEntregasPorTp;
	}
	
	/**
	 * Esta es otra forma de implementar el metodo anterior
	 * en caso que el tp no conozca todas las entregas y 
	 * le tenga que preguntar a cada alumno la nota de un tp,
	 * un alumno sabe responder correctamente que nota se saco 
	 * en un Tp.
	 * 
	 * @return
	 */
	public List<EntregaTpPorAlumno> mejorEntregaPorTp(){
		List<EntregaTpPorAlumno> mejoresEntregasPorTp = new ArrayList<EntregaTpPorAlumno>();
		for (Tp tp : this.getTps()) {
			EntregaTpPorAlumno mejorEntrega = this.primerAlumno().getEntrega(tp);
			for (AlumnoEnCatedra alumno : this.getAlumnos()) { 
				if(alumno.getEntrega(tp).compareTo(mejorEntrega) == -1 ){
					mejorEntrega = alumno.getEntrega(tp);
				}
			}
			mejoresEntregasPorTp.add(mejorEntrega);
		}
		return mejoresEntregasPorTp;
	}

	/**
	 * Devuelve el promedio total de notas de 
	 * todos los alumnos para un mismo Tp
	 * @param unTp
	 * @return
	 */
	public Integer promedioPorTp(Tp unTp){
		Integer notasTotalTp = 0;
		for (AlumnoEnCatedra alumno : this.getAlumnos()) {
			notasTotalTp = notasTotalTp + alumno.notaEnTp(unTp); 
		}
		return notasTotalTp / this.cantidadDeAlumnos();
	}
	
	/**
	 * Devuelve el promedio total de notas de alumnos 
	 * por todos los tps del curso
	 * 
	 * @return
	 */
	public Integer promedioTotalTps(){
		Integer totNotas = 0;
		for (Tp tp : this.getTps()) {
			totNotas = totNotas + this.promedioPorTp(tp);
		}
		
		return (totNotas / this.cantidadTps());
	}
		
}